package com.s.core.root;

import com.google.common.collect.Lists;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;

/**
 * @author zhangjiangnan
 */
@Data
public class D {

    public static final long WEAK_ZT_20_CM = 14L;
    public static final long WEAK_ZT_30_CM = 21L;

    public D() {
    }

    public D(String code, String date) {
        this.code = code;
        this.date = date;
    }

    public D(String code, String date, String close, String vol, String preClose, String high, int pos1,
             int pos2, int pos3, int pos4, int pos5, int pos6, int pos7, int pos8, int pos9, int pos10,
             int pos11, int pos12, int pos13, int pos14, int pos15, int pos16, int pos17, int pos18, int pos19, int pos20,
             int pos21, int pos22, int pos23, int pos24, int pos25) {
        this.code = code;
        this.date = date;
        this.close = close;
        this.vol = vol;
        this.preClose = preClose;
        this.high = high;
        this.pos1 = pos1;
        this.pos2 = pos2;
        this.pos3 = pos3;
        this.pos4 = pos4;
        this.pos5 = pos5;
        this.pos6 = pos6;
        this.pos7 = pos7;
        this.pos8 = pos8;
        this.pos9 = pos9;
        this.pos10 = pos10;
        this.pos11 = pos11;
        this.pos12 = pos12;
        this.pos13 = pos13;
        this.pos14 = pos14;
        this.pos15 = pos15;
        this.pos16 = pos16;
        this.pos17 = pos17;
        this.pos18 = pos18;
        this.pos19 = pos19;
        this.pos20 = pos20;
        this.pos21 = pos21;
        this.pos22 = pos22;
        this.pos23 = pos23;
        this.pos24 = pos24;
        this.pos25 = pos25;
    }

    public String code;
    public String date;
    public String close;
    public String vol;
    public String preClose;
    public String high;
    public int pos1;
    public int pos2;
    public int pos3;
    public int pos4;
    public int pos5;
    public int pos6;
    public int pos7;
    public int pos8;
    public int pos9;
    public int pos10;
    public int pos11;
    public int pos12;
    public int pos13;
    public int pos14;
    public int pos15;
    public int pos16;
    public int pos17;
    public int pos18;
    public int pos19;
    public int pos20;
    public int pos21;
    public int pos22;
    public int pos23;
    public int pos24;
    public int pos25;
    public int pos30;
    public int pos40;
    public int pos50;
    public int pos60;
    public int pos80;
    public int pos100;
    public int pos120;


    public void setPosN(int n, int pos) {
        if (n == 1) this.pos1 = pos;
        if (n == 2) this.pos2 = pos;
        if (n == 3) this.pos3 = pos;
        if (n == 4) this.pos4 = pos;
        if (n == 5) this.pos5 = pos;
        if (n == 6) this.pos6 = pos;
        if (n == 7) this.pos7 = pos;
        if (n == 8) this.pos8 = pos;
        if (n == 9) this.pos9 = pos;
        if (n == 10) this.pos10 = pos;
        if (n == 11) this.pos11 = pos;
        if (n == 12) this.pos12 = pos;
        if (n == 13) this.pos13 = pos;
        if (n == 14) this.pos14 = pos;
        if (n == 15) this.pos15 = pos;
        if (n == 16) this.pos16 = pos;
        if (n == 17) this.pos17 = pos;
        if (n == 18) this.pos18 = pos;
        if (n == 19) this.pos19 = pos;
        if (n == 20) this.pos20 = pos;
        if (n == 21) this.pos21 = pos;
        if (n == 22) this.pos22 = pos;
        if (n == 23) this.pos23 = pos;
        if (n == 24) this.pos24 = pos;
        if (n == 25) this.pos25 = pos;
        if (n == 30) this.pos30 = pos;
        if (n == 40) this.pos40 = pos;
        if (n == 50) this.pos50 = pos;
        if (n == 60) this.pos60 = pos;
        if (n == 80) this.pos80 = pos;
        if (n == 100) this.pos100 = pos;
        if (n == 120) this.pos120 = pos;
    }

    public int getPosN(int n) {
        if (n == 1) return pos1;
        if (n == 2) return pos2;
        if (n == 3) return pos3;
        if (n == 4) return pos4;
        if (n == 5) return pos5;
        if (n == 6) return pos6;
        if (n == 7) return pos7;
        if (n == 8) return pos8;
        if (n == 9) return pos9;
        if (n == 10) return pos10;
        if (n == 11) return pos11;
        if (n == 12) return pos12;
        if (n == 13) return pos13;
        if (n == 14) return pos14;
        if (n == 15) return pos15;
        if (n == 16) return pos16;
        if (n == 17) return pos17;
        if (n == 18) return pos18;
        if (n == 19) return pos19;
        if (n == 20) return pos20;
        if (n == 21) return pos21;
        if (n == 22) return pos22;
        if (n == 23) return pos23;
        if (n == 24) return pos24;
        if (n == 25) return pos25;
        if (n == 30) return pos30;
        if (n == 40) return pos40;
        if (n == 50) return pos50;
        if (n == 60) return pos60;
        if (n == 80) return pos80;
        if (n == 100) return pos100;
        if (n == 120) return pos120;
        throw new RuntimeException();
    }

    public Integer getPosAvg(int avgCount) {
        int totalPos = 0;
        int count = 0;
        for (int n = 1; n <= avgCount; n++) {
            count++;
            totalPos += getPosN(n);
        }
        return count == 0 ? 0 : (totalPos / count);
    }

    public boolean is20cm() {
        return CodeFeature.getCmType(code) == CodeFeature.CM_TYPE._20;
    }

    public boolean is30cm() {
        return CodeFeature.getCmType(code) == CodeFeature.CM_TYPE._30;
    }

    public boolean isNot10cm() {
        return is20cm() || is30cm();
    }

    //select code from daily1 where date = 20241219 and close is null;
    public boolean isTp() {
        //停牌close vol pre_close high 均为NULL, pos1-pos10为0,pos_avg也为0, vol2-vol10不一定为0是取平均的逻辑
        //close 和 vol 不为 null , pos1 为 0 表示新股第一天
        //close 和 vol 不为 null , pos1 不为 0, pos2-pos10为0 表示前面停牌
        return close == null || vol == null;
    }

    public boolean isZt() {
        if (isTp()) {
            return false;
        }
        if (is20cm()) {
            return isZt(1.2, RoundingMode.HALF_UP);
        } else if (is30cm()) {
            return isZt(1.3, RoundingMode.HALF_UP) || isZt(1.3, RoundingMode.DOWN);
        } else {
            return isZt(1.1, RoundingMode.HALF_UP);
        }
    }

    public boolean isWeakZt() {
        return is20cm() && Optional.ofNullable(zf(1, true)).orElse(0d) >= WEAK_ZT_20_CM ||
                is30cm() && Optional.ofNullable(zf(1, true)).orElse(0d) >= WEAK_ZT_30_CM;
    }

    private boolean isZt(double d, RoundingMode roundingMode) {
        return new BigDecimal(preClose).multiply(BigDecimal.valueOf(d)).
                setScale(2, roundingMode).
                compareTo(new BigDecimal(close)) == 0;
    }

    public D bDaily(int offset) {
        String bDate = Ad.me().bDate(date, offset);
        if (bDate == null) {
            return null;
        }
        return Ad.me().getDaily(bDate, code);
    }

    public List<D> bDailyList(Integer days) {
        List<D> dailyList = Ad.me().getCodeDailyListMap().get(code);
        dailyList = dailyList.subList(dailyList.indexOf(this), dailyList.size());
        if (days == null) {
            return dailyList;
        }
        return dailyList.subList(0, Math.min(days, dailyList.size()));
    }

    public Double zf(int offset, boolean useClose) {
        if (isTp()) {
            return null;
        }
        List<D> bDailyList = Lists.reverse(bDailyList(offset + 1));
        if (bDailyList.size() != offset + 1 || bDailyList.get(0).isTp()) {
            return null;
        }
        BigDecimal bClose = new BigDecimal(bDailyList.get(0).close);
        D bD = bDailyList.get(0);
        BigDecimal adjFactor = new BigDecimal(1);
        for (int i = 1; i < bDailyList.size(); i++) {
            D aD = bDailyList.get(i);
            if (aD.isTp()) {
                continue;
            }
            if (!StringUtils.equals(bD.close, aD.preClose)) {
                adjFactor = adjFactor.multiply(new BigDecimal(bD.close)).divide(new BigDecimal(aD.preClose), 5, RoundingMode.HALF_UP);
            }
            bD = aD;
        }
        if (adjFactor.compareTo(new BigDecimal(1)) != 0) {
            bClose = bClose.divide(adjFactor, 4, RoundingMode.HALF_UP);
        }
        String closeOrHigh = useClose ? close : high;
        BigDecimal increaseRate = (new BigDecimal(closeOrHigh).subtract(bClose)).
                divide(bClose, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"));
        return increaseRate.doubleValue();
    }

    public Integer syValue() {
        if (isSy(0.9f)) {
            return 9;
        } else if (isSy(0.8f)) {
            return 8;
        } else if (isSy(0.7f)) {
            return 7;
        } else if (isSy(0.6f)) {
            return 6;
        } else if (isSy(0.5f)) {
            return 5;
        }
        return null;
    }

    public boolean isContainsSy(int days, float[] syRates) {
        int index = Ad.me().getDateList().indexOf(date);
        for (int i = index; i < index + days; i++) {
            String date = Ad.me().getDateList().get(i);
            D d = Ad.me().getDaily(date, code);
            if (d == null) {
                continue;
            }
            for (float syRate : syRates) {
                if (d.isSy(syRate)) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean isSy(float syRate) {
        if (isTp()) {
            return false;
        }
        Double zf = zf(1, false);
        if (zf == null) {
            return false;
        }
        BigDecimal highZf = BigDecimal.valueOf(zf);
        long sy;
        if (is20cm()) {
            sy = WEAK_ZT_20_CM;
        } else if (is30cm()) {
            sy = WEAK_ZT_30_CM;
        } else {
            sy = 10L;
        }
        BigDecimal b = BigDecimal.valueOf(syRate).multiply(BigDecimal.valueOf(sy));
        return highZf.subtract(b).floatValue() > 0;
    }

    public int ztLastCounts() {
        int result = 0;
        if (!isZt()) {
            return result;
        }
        int index = Ad.me().getDateList().indexOf(date);
        for (int i = index; i < Ad.me().getDateList().size(); i++) {
            String date = Ad.me().getDateList().get(i);
            D d = Ad.me().getDaily(date, code);
            if (d == null || !d.isZt()) {
                return result;
            }
            result++;
        }
        return result;
    }

    public boolean isContainsZt(int days, boolean includeWeakZt) {
        return ztCounts(days, includeWeakZt) > 0;
    }

    public int ztCounts(int days, boolean includeWeakZt) {
        int result = 0;
        int index = Ad.me().getDateList().indexOf(date);
        for (int i = index; i < index + days; i++) {
            String date = Ad.me().getDateList().get(i);
            D d = Ad.me().getDaily(date, code);
            if (d == null) {
                continue;
            }
            if (d.isZt()) {
                result++;
                continue;
            }
            if (includeWeakZt && d.isWeakZt()) {
                result++;
            }
        }
        return result;
    }

    public String volN(int days) {
        BigDecimal maVol = ma(days, d -> new BigDecimal(d.getVol()));
        return maVol == null ? null : maVol.toBigInteger().toString();
    }

    public String volNAvg(int days) {
        BigDecimal sum = BigDecimal.ZERO;
        int count = 0;
        for (int day = 1; day <= days; day++) {
            BigDecimal maVol = ma(day, d -> new BigDecimal(d.getVol()));
            if (maVol == null) {
                continue;
            }
            sum = sum.add(maVol);
            count++;
        }
        if (count > 0) {
            return sum.divide(BigDecimal.valueOf(count), 2, RoundingMode.HALF_UP).toBigInteger().toString();
        } else {
            return null;
        }
    }

    private BigDecimal ma(int days, Function<D, BigDecimal> toBigDecimalFunction) {
        BigDecimal sum = BigDecimal.ZERO;
        int count = 0;
        for (D bDaily : bDailyList(days)) {
            if (bDaily.isTp()) {
                continue;
            }
            BigDecimal addBigDecimal = toBigDecimalFunction.apply(bDaily);
            if (addBigDecimal == null) {
                continue;
            }
            sum = sum.add(addBigDecimal);
            count++;
        }
        if (count > 0) {
            return sum.divide(BigDecimal.valueOf(count), 2, RoundingMode.HALF_UP);
        } else {
            return null;
        }
    }

    @Override
    public boolean equals(Object object) {
        if (object instanceof D) {
            return ((D) object).getCode().equals(code) &&
                    ((D) object).getDate().equals(date);
        }
        return false;
    }

    @Override
    public String toString() {
        return "code" + ":" + code + "," + "date" + ":" + date + ",";
    }
}
